"""
TypeScript type system analyzer.

Analyzes TypeScript type definitions, interfaces, generics, and type relationships
to understand code structure and dependencies.
"""

from typing import Dict, List, Any, Optional, Set, Tuple, Union
from collections import defaultdict
import re

from ...core.error_handler import AnalysisError, log_info


class TypeScriptTypeAnalyzer:
    """Analyzes TypeScript type system and type relationships."""
    
    def __init__(self):
        """Initialize the type analyzer."""
        self.type_cache = {}
        self.interface_hierarchy = defaultdict(set)
        self.type_aliases = {}
        self.generic_constraints = {}
    
    def analyze_types(self, file_content: str, file_path: str) -> Dict[str, Any]:
        """Analyze TypeScript types in a file."""
        try:
            interfaces = self._extract_interfaces(file_content)
            type_aliases = self._extract_type_aliases(file_content)
            enums = self._extract_enums(file_content)
            classes = self._extract_class_types(file_content)
            generics = self._extract_generic_types(file_content)
            
            return {
                'file_path': file_path,
                'interfaces': interfaces,
                'type_aliases': type_aliases,
                'enums': enums,
                'classes': classes,
                'generics': generics,
                'type_relationships': self._analyze_type_relationships(interfaces, type_aliases, classes),
                'utility_types': self._detect_utility_types(file_content),
                'conditional_types': self._extract_conditional_types(file_content)
            }
            
        except Exception as e:
            raise AnalysisError(f"Failed to analyze types in {file_path}: {str(e)}")
    
    def _extract_interfaces(self, content: str) -> List[Dict[str, Any]]:
        """Extract interface declarations."""
        interfaces = []
        lines = content.split('\n')
        
        i = 0
        while i < len(lines):
            line = lines[i].strip()
            
            # Interface declaration pattern
            interface_match = re.match(r'(?:export\s+)?interface\s+(\w+)(?:<([^>]+)>)?\s*(?:extends\s+([^{]+))?\s*\{', line)
            if interface_match:
                interface_name = interface_match.group(1)
                generics = interface_match.group(2)
                extends = interface_match.group(3)
                
                # Extract interface body
                properties, methods, i = self._extract_interface_body(lines, i + 1)
                
                interface_info = {
                    'name': interface_name,
                    'line': i + 1,
                    'exported': 'export' in line,
                    'properties': properties,
                    'methods': methods,
                    'extends': [ext.strip() for ext in extends.split(',')] if extends else [],
                    'generics': self._parse_generics(generics) if generics else []
                }
                
                interfaces.append(interface_info)
                
                # Update hierarchy
                for parent in interface_info['extends']:
                    self.interface_hierarchy[parent].add(interface_name)
            else:
                i += 1
        
        return interfaces
    
    def _extract_interface_body(self, lines: List[str], start_line: int) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], int]:
        """Extract properties and methods from interface body."""
        properties = []
        methods = []
        brace_count = 1
        i = start_line
        
        while i < len(lines) and brace_count > 0:
            line = lines[i].strip()
            
            # Count braces to find end of interface
            brace_count += line.count('{') - line.count('}')
            
            if brace_count > 0 and line and not line.startswith('//'):
                # Property or method
                if '(' in line and ')' in line:
                    # Method
                    method_info = self._parse_method_signature(line, i + 1)
                    if method_info:
                        methods.append(method_info)
                else:
                    # Property
                    prop_info = self._parse_property_signature(line, i + 1)
                    if prop_info:
                        properties.append(prop_info)
            
            i += 1
        
        return properties, methods, i
    
    def _parse_method_signature(self, line: str, line_num: int) -> Optional[Dict[str, Any]]:
        """Parse method signature from interface."""
        # Method pattern: methodName(params): returnType
        method_pattern = r'(\w+)(?:<([^>]+)>)?\s*\(([^)]*)\)\s*:\s*([^;]+)'
        match = re.search(method_pattern, line)
        
        if match:
            return {
                'name': match.group(1),
                'generics': self._parse_generics(match.group(2)) if match.group(2) else [],
                'parameters': self._parse_parameters(match.group(3)),
                'return_type': match.group(4).strip(),
                'line': line_num,
                'optional': '?' in line,
                'raw': line
            }
        
        return None
    
    def _parse_property_signature(self, line: str, line_num: int) -> Optional[Dict[str, Any]]:
        """Parse property signature from interface."""
        # Property pattern: propertyName?: type
        prop_pattern = r'(\w+)(\?)?:\s*([^;]+)'
        match = re.search(prop_pattern, line)
        
        if match:
            return {
                'name': match.group(1),
                'type': match.group(3).strip(),
                'optional': match.group(2) == '?',
                'readonly': 'readonly' in line,
                'line': line_num,
                'raw': line
            }
        
        return None
    
    def _extract_type_aliases(self, content: str) -> List[Dict[str, Any]]:
        """Extract type alias declarations."""
        type_aliases = []
        
        # Type alias pattern
        type_pattern = r'(?:export\s+)?type\s+(\w+)(?:<([^>]+)>)?\s*=\s*([^;]+);?'
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            line = line.strip()
            match = re.search(type_pattern, line)
            
            if match:
                alias_name = match.group(1)
                generics = match.group(2)
                alias_type = match.group(3).strip()
                
                type_info = {
                    'name': alias_name,
                    'type': alias_type,
                    'generics': self._parse_generics(generics) if generics else [],
                    'exported': 'export' in line,
                    'line': line_num,
                    'raw': line,
                    'category': self._categorize_type_alias(alias_type)
                }
                
                type_aliases.append(type_info)
                self.type_aliases[alias_name] = type_info
        
        return type_aliases
    
    def _extract_enums(self, content: str) -> List[Dict[str, Any]]:
        """Extract enum declarations."""
        enums = []
        lines = content.split('\n')
        
        i = 0
        while i < len(lines):
            line = lines[i].strip()
            
            # Enum declaration pattern
            enum_match = re.match(r'(?:export\s+)?(?:const\s+)?enum\s+(\w+)\s*\{', line)
            if enum_match:
                enum_name = enum_match.group(1)
                
                # Extract enum members
                members, i = self._extract_enum_members(lines, i + 1)
                
                enum_info = {
                    'name': enum_name,
                    'members': members,
                    'exported': 'export' in line,
                    'const': 'const' in line,
                    'line': i + 1,
                    'numeric': self._is_numeric_enum(members)
                }
                
                enums.append(enum_info)
            else:
                i += 1
        
        return enums
    
    def _extract_enum_members(self, lines: List[str], start_line: int) -> Tuple[List[Dict[str, Any]], int]:
        """Extract members from enum body."""
        members = []
        brace_count = 1
        i = start_line
        
        while i < len(lines) and brace_count > 0:
            line = lines[i].strip()
            
            # Count braces to find end of enum
            brace_count += line.count('{') - line.count('}')
            
            if brace_count > 0 and line and not line.startswith('//'):
                # Parse enum member
                member_match = re.match(r'(\w+)(?:\s*=\s*(.+?))?[,}]?', line)
                if member_match:
                    member_name = member_match.group(1)
                    member_value = member_match.group(2)
                    
                    members.append({
                        'name': member_name,
                        'value': member_value.strip() if member_value else None,
                        'line': i + 1
                    })
            
            i += 1
        
        return members, i
    
    def _extract_class_types(self, content: str) -> List[Dict[str, Any]]:
        """Extract class type information."""
        classes = []
        lines = content.split('\n')
        
        i = 0
        while i < len(lines):
            line = lines[i].strip()
            
            # Class declaration pattern
            class_match = re.match(r'(?:export\s+)?(?:abstract\s+)?class\s+(\w+)(?:<([^>]+)>)?\s*(?:extends\s+([^{]+?))?\s*(?:implements\s+([^{]+?))?\s*\{', line)
            if class_match:
                class_name = class_match.group(1)
                generics = class_match.group(2)
                extends = class_match.group(3)
                implements = class_match.group(4)
                
                # Extract class body for type information
                properties, methods, i = self._extract_class_type_info(lines, i + 1)
                
                class_info = {
                    'name': class_name,
                    'generics': self._parse_generics(generics) if generics else [],
                    'extends': extends.strip() if extends else None,
                    'implements': [impl.strip() for impl in implements.split(',')] if implements else [],
                    'properties': properties,
                    'methods': methods,
                    'abstract': 'abstract' in line,
                    'exported': 'export' in line,
                    'line': i + 1
                }
                
                classes.append(class_info)
            else:
                i += 1
        
        return classes
    
    def _extract_class_type_info(self, lines: List[str], start_line: int) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], int]:
        """Extract type information from class body."""
        properties = []
        methods = []
        brace_count = 1
        i = start_line
        
        while i < len(lines) and brace_count > 0:
            line = lines[i].strip()
            
            # Count braces to find end of class
            brace_count += line.count('{') - line.count('}')
            
            if brace_count > 0 and line and not line.startswith('//'):
                # Skip constructor and method bodies
                if '{' in line:
                    i += 1
                    continue
                
                # Property or method declaration
                if '(' in line and ')' in line and ':' in line:
                    # Method
                    method_info = self._parse_class_method_type(line, i + 1)
                    if method_info:
                        methods.append(method_info)
                elif ':' in line:
                    # Property
                    prop_info = self._parse_class_property_type(line, i + 1)
                    if prop_info:
                        properties.append(prop_info)
            
            i += 1
        
        return properties, methods, i
    
    def _parse_class_method_type(self, line: str, line_num: int) -> Optional[Dict[str, Any]]:
        """Parse method type from class."""
        # Method pattern with visibility
        method_pattern = r'(?:(public|private|protected|static)\s+)?(\w+)(?:<([^>]+)>)?\s*\(([^)]*)\)\s*:\s*([^{;]+)'
        match = re.search(method_pattern, line)
        
        if match:
            return {
                'name': match.group(2),
                'visibility': match.group(1) or 'public',
                'generics': self._parse_generics(match.group(3)) if match.group(3) else [],
                'parameters': self._parse_parameters(match.group(4)),
                'return_type': match.group(5).strip(),
                'line': line_num,
                'static': 'static' in line,
                'abstract': 'abstract' in line
            }
        
        return None
    
    def _parse_class_property_type(self, line: str, line_num: int) -> Optional[Dict[str, Any]]:
        """Parse property type from class."""
        # Property pattern with visibility
        prop_pattern = r'(?:(public|private|protected|static|readonly)\s+)*(\w+)(\?)?:\s*([^;=]+)'
        match = re.search(prop_pattern, line)
        
        if match:
            modifiers = match.group(1).split() if match.group(1) else []
            return {
                'name': match.group(2),
                'type': match.group(4).strip(),
                'visibility': next((mod for mod in modifiers if mod in ['public', 'private', 'protected']), 'public'),
                'optional': match.group(3) == '?',
                'readonly': 'readonly' in modifiers,
                'static': 'static' in modifiers,
                'line': line_num
            }
        
        return None
    
    def _extract_generic_types(self, content: str) -> List[Dict[str, Any]]:
        """Extract generic type information."""
        generics = []
        
        # Generic patterns in various contexts
        generic_patterns = [
            r'<([^>]+)>',  # Any generic declaration
        ]
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            for pattern in generic_patterns:
                matches = re.finditer(pattern, line)
                for match in matches:
                    generic_params = self._parse_generics(match.group(1))
                    if generic_params:
                        generics.extend([{
                            'name': param['name'],
                            'constraint': param.get('constraint'),
                            'default': param.get('default'),
                            'line': line_num,
                            'context': line.strip()
                        } for param in generic_params])
        
        return generics
    
    def _parse_generics(self, generic_string: str) -> List[Dict[str, Any]]:
        """Parse generic type parameters."""
        if not generic_string:
            return []
        
        generics = []
        params = [param.strip() for param in generic_string.split(',')]
        
        for param in params:
            # Parse T extends U = V
            parts = param.split('=')
            default = parts[1].strip() if len(parts) > 1 else None
            
            constraint_parts = parts[0].split(' extends ')
            name = constraint_parts[0].strip()
            constraint = constraint_parts[1].strip() if len(constraint_parts) > 1 else None
            
            generics.append({
                'name': name,
                'constraint': constraint,
                'default': default
            })
        
        return generics
    
    def _parse_parameters(self, param_string: str) -> List[Dict[str, Any]]:
        """Parse function/method parameters."""
        if not param_string.strip():
            return []
        
        parameters = []
        params = [param.strip() for param in param_string.split(',')]
        
        for param in params:
            # Parse name?: type = default
            parts = param.split('=')
            default = parts[1].strip() if len(parts) > 1 else None
            
            type_parts = parts[0].split(':')
            name_part = type_parts[0].strip()
            param_type = type_parts[1].strip() if len(type_parts) > 1 else 'any'
            
            optional = name_part.endswith('?')
            name = name_part.rstrip('?')
            
            parameters.append({
                'name': name,
                'type': param_type,
                'optional': optional,
                'default': default,
                'rest': name.startswith('...')
            })
        
        return parameters
    
    def _categorize_type_alias(self, alias_type: str) -> str:
        """Categorize type alias based on its definition."""
        if '|' in alias_type:
            return 'union'
        elif '&' in alias_type:
            return 'intersection'
        elif alias_type.startswith('keyof'):
            return 'keyof'
        elif alias_type.startswith('typeof'):
            return 'typeof'
        elif re.search(r'\w+<[^>]+>', alias_type):
            return 'generic'
        elif '{' in alias_type and '}' in alias_type:
            return 'object'
        elif '[' in alias_type and ']' in alias_type:
            return 'array'
        else:
            return 'simple'
    
    def _is_numeric_enum(self, members: List[Dict[str, Any]]) -> bool:
        """Check if enum is numeric."""
        for member in members:
            value = member.get('value')
            if value and not value.isdigit():
                return False
        return True
    
    def _analyze_type_relationships(self, interfaces: List[Dict[str, Any]], 
                                   type_aliases: List[Dict[str, Any]], 
                                   classes: List[Dict[str, Any]]) -> Dict[str, Any]:
        """Analyze relationships between types."""
        relationships = {
            'inheritance': defaultdict(list),
            'implementation': defaultdict(list),
            'composition': defaultdict(list),
            'type_usage': defaultdict(list)
        }
        
        # Interface inheritance
        for interface in interfaces:
            for parent in interface.get('extends', []):
                relationships['inheritance'][parent].append(interface['name'])
        
        # Class inheritance and implementation
        for class_info in classes:
            if class_info.get('extends'):
                relationships['inheritance'][class_info['extends']].append(class_info['name'])
            
            for impl in class_info.get('implements', []):
                relationships['implementation'][impl].append(class_info['name'])
        
        # Type usage analysis
        all_types = [i['name'] for i in interfaces] + [t['name'] for t in type_aliases] + [c['name'] for c in classes]
        
        for interface in interfaces:
            for prop in interface.get('properties', []):
                for type_name in all_types:
                    if type_name in prop['type']:
                        relationships['type_usage'][type_name].append(f"{interface['name']}.{prop['name']}")
        
        return dict(relationships)
    
    def _detect_utility_types(self, content: str) -> List[Dict[str, Any]]:
        """Detect usage of TypeScript utility types."""
        utility_types = [
            'Partial', 'Required', 'Readonly', 'Record', 'Pick', 'Omit',
            'Exclude', 'Extract', 'NonNullable', 'ReturnType', 'InstanceType',
            'Parameters', 'ConstructorParameters'
        ]
        
        detected = []
        lines = content.split('\n')
        
        for line_num, line in enumerate(lines, 1):
            for util_type in utility_types:
                pattern = rf'\b{util_type}<([^>]+)>'
                matches = re.finditer(pattern, line)
                for match in matches:
                    detected.append({
                        'type': util_type,
                        'parameters': match.group(1),
                        'line': line_num,
                        'context': line.strip()
                    })
        
        return detected
    
    def _extract_conditional_types(self, content: str) -> List[Dict[str, Any]]:
        """Extract conditional type expressions."""
        conditional_types = []
        
        # Pattern for conditional types: T extends U ? X : Y
        conditional_pattern = r'(\w+)\s+extends\s+([^?]+)\s*\?\s*([^:]+)\s*:\s*(.+)'
        
        lines = content.split('\n')
        for line_num, line in enumerate(lines, 1):
            matches = re.finditer(conditional_pattern, line)
            for match in matches:
                conditional_types.append({
                    'check_type': match.group(1),
                    'extends_type': match.group(2).strip(),
                    'true_type': match.group(3).strip(),
                    'false_type': match.group(4).strip(),
                    'line': line_num,
                    'raw': line.strip()
                })
        
        return conditional_types


class TypeInferenceEngine:
    """Infers types from TypeScript code context."""
    
    def __init__(self):
        """Initialize the type inference engine."""
        self.inference_cache = {}
        self.type_mappings = {}
    
    def infer_variable_type(self, variable_name: str, context: str) -> Optional[str]:
        """Infer the type of a variable from its context."""
        # Look for explicit type annotations
        type_annotation_pattern = rf'{variable_name}\s*:\s*([^=;,)]+)'
        match = re.search(type_annotation_pattern, context)
        if match:
            return match.group(1).strip()
        
        # Look for initialization values
        init_pattern = rf'{variable_name}\s*=\s*([^;,)]+)'
        match = re.search(init_pattern, context)
        if match:
            value = match.group(1).strip()
            return self._infer_from_value(value)
        
        return None
    
    def _infer_from_value(self, value: str) -> str:
        """Infer type from a value."""
        value = value.strip()
        
        # String literals
        if (value.startswith('"') and value.endswith('"')) or \
           (value.startswith("'") and value.endswith("'")):
            return 'string'
        
        # Number literals
        if value.isdigit() or re.match(r'^\d+\.\d+$', value):
            return 'number'
        
        # Boolean literals
        if value in ['true', 'false']:
            return 'boolean'
        
        # Array literals
        if value.startswith('[') and value.endswith(']'):
            return 'array'
        
        # Object literals
        if value.startswith('{') and value.endswith('}'):
            return 'object'
        
        # Function expressions
        if 'function' in value or '=>' in value:
            return 'function'
        
        # null/undefined
        if value in ['null', 'undefined']:
            return value
        
        return 'unknown'
    
    def analyze_type_flow(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
        """Analyze type flow through the code."""
        type_flow = {
            'assignments': [],
            'function_calls': [],
            'property_accesses': [],
            'type_guards': []
        }
        
        # This would be implemented with more sophisticated analysis
        # of the AST or type information from the TypeScript compiler
        
        return type_flow